#include "ics_types.h"

namespace icslib {

    std::string Date::format() const {
        char temp[17] = {'\0'};
        sprintf(temp, "%.4d/%.2d/%.2d", m_nArrData[enum_index(TimeUnit::kYear)], m_nArrData[enum_index(TimeUnit::kMonth)], m_nArrData[enum_index(TimeUnit::kDay)] );
        if(m_bWithTime) {
            sprintf(temp + 10, "%.2d:%.2d", m_nArrData[enum_index(TimeUnit::kHour)], m_nArrData[enum_index(TimeUnit::kMinute)]);
        }
        return std::string(temp);
    }

    Date::operator std::string() const {
        char temp[18] = {'\0'};
        sprintf(temp, "%.4d/%.2d/%.2d", m_nArrData[enum_index(TimeUnit::kYear)], m_nArrData[enum_index(TimeUnit::kMonth)], m_nArrData[enum_index(TimeUnit::kDay)] );
        if(m_bWithTime) {
            sprintf(temp + 10, "T%.2d%.2d%.2d", m_nArrData[enum_index(TimeUnit::kHour)], m_nArrData[enum_index(TimeUnit::kMinute)], m_nArrData[enum_index(TimeUnit::kSecond)]);
        }
        return std::string(temp);
    }

    /**
     * @brief Date::operator =  赋值
     * @param text
     * @return
     */
    Date &Date::operator =(const std::string &text) {
        if(text.length() >= 8) {
            sscanf(text.c_str(), "%4hd%2hd%2hd", &m_nArrData[enum_index(TimeUnit::kYear)], &m_nArrData[enum_index(TimeUnit::kMonth)], &m_nArrData[enum_index(TimeUnit::kDay)]);
            if(text.length() >= 15) {
                sscanf(text.c_str()+9, "%2hd%2hd%2hd", &m_nArrData[enum_index(TimeUnit::kHour)], &m_nArrData[enum_index(TimeUnit::kMinute)], &m_nArrData[enum_index(TimeUnit::kSecond)]);
                m_bWithTime = true;
            }else {
               m_nArrData[enum_index(TimeUnit::kHour)] = 0;
               m_nArrData[enum_index(TimeUnit::kMinute)] = 0;
               m_nArrData[enum_index(TimeUnit::kSecond)] = 0;
               m_bWithTime = false;
            }
        }else {
            m_nArrData[enum_index(TimeUnit::kYear)] = 0;
            m_nArrData[enum_index(TimeUnit::kMonth)] = 0;
            m_nArrData[enum_index(TimeUnit::kDay)] = 0;
        }
        return *this;
    }

    unsigned long Date::difference(Date &otherDate, TimeUnit unit, bool roundUp ) const {
        unsigned long result = 0;
        switch (unit) {
        case TimeUnit::kYear:
            result = m_nArrData[enum_index(TimeUnit::kYear)] - otherDate.m_nArrData[enum_index(TimeUnit::kYear)];
            break;
        case TimeUnit::kMonth:
            result = (m_nArrData[enum_index(TimeUnit::kYear)] - otherDate.m_nArrData[enum_index(TimeUnit::kYear)]) * 12 + \
                    m_nArrData[enum_index(TimeUnit::kMonth)] - otherDate.m_nArrData[enum_index(TimeUnit::kMonth)];
            break;
        case TimeUnit::kDay:
             if(otherDate.m_nArrData[enum_index(TimeUnit::kYear)] == m_nArrData[enum_index(TimeUnit::kYear)]) {
                 if(otherDate.m_nArrData[enum_index(TimeUnit::kMonth)] == m_nArrData[enum_index(TimeUnit::kMonth)]) {
                     result = m_nArrData[enum_index(TimeUnit::kDay)] - otherDate.m_nArrData[enum_index(TimeUnit::kDay)];
                 } else {
                     result = (otherDate.daysInMonth() - otherDate.m_nArrData[enum_index(TimeUnit::kDay)] ) + m_nArrData[enum_index(TimeUnit::kDay)];
                     for (char i = otherDate.m_nArrData[enum_index(TimeUnit::kMonth)]+1; i < m_nArrData[enum_index(TimeUnit::kMonth)]; ++i) {
                         result += otherDate.daysInMonth(i);
                     }
                 }
            } else {
                result = otherDate.daysInMonth() - otherDate.m_nArrData[enum_index(TimeUnit::kDay)];
                for (char i = otherDate.m_nArrData[enum_index(TimeUnit::kMonth)]; i <=12; ++i) {
                    result+=otherDate.daysInMonth(i);
                }
                for (char i = otherDate.m_nArrData[enum_index(TimeUnit::kYear)] + 1; i < m_nArrData[enum_index(TimeUnit::kYear)]; ++i) {
                    result += isLeapYear(i) ? 366 : 365;
                }
                for (char i = 1; i < m_nArrData[enum_index(TimeUnit::kMonth)]; ++i) {
                    result += daysInMonth(i);
                }
                result += m_nArrData[enum_index(TimeUnit::kDay)];
            }
            break;

        case TimeUnit::kHour:
            if(m_nArrData[enum_index(TimeUnit::kDay)] == otherDate.m_nArrData[enum_index(TimeUnit::kDay)]) {
                result = m_nArrData[enum_index(TimeUnit::kHour)] - otherDate.m_nArrData[enum_index(TimeUnit::kHour)];
            }else {
                result = 24- otherDate.m_nArrData[enum_index(TimeUnit::kHour)];
                result += (difference(otherDate, TimeUnit::kDay, false) -1) * 24;
                result += m_nArrData[enum_index(TimeUnit::kHour)];
            }
            break;

        case TimeUnit::kMinute:
            if(m_nArrData[enum_index(TimeUnit::kHour)] == otherDate.m_nArrData[enum_index(TimeUnit::kHour)]) {
                result = m_nArrData[enum_index(TimeUnit::kMinute)] - otherDate.m_nArrData[enum_index(TimeUnit::kMinute)];
            }else {
                result = 24- otherDate.m_nArrData[enum_index(TimeUnit::kMinute)];
                result += (difference(otherDate, TimeUnit::kHour, false) -1) * 60;
                result += m_nArrData[enum_index(TimeUnit::kMinute)];
            }
            break;
        case TimeUnit::kSecond:
            if(m_nArrData[enum_index(TimeUnit::kMinute)] == otherDate.m_nArrData[enum_index(TimeUnit::kMinute)]) {
                result = m_nArrData[enum_index(TimeUnit::kSecond)] - otherDate.m_nArrData[enum_index(TimeUnit::kSecond)];
            }else {
                result = 24- otherDate.m_nArrData[enum_index(TimeUnit::kSecond)];
                result += (difference(otherDate, TimeUnit::kMinute, false) -1) * 60;
                result += m_nArrData[enum_index(TimeUnit::kSecond)];
            }
            break;

        case TimeUnit::kWeek:
             result = difference(otherDate, TimeUnit::kDay, false) /7 +1;
            break;
        }
        if (roundUp == true && unit < TimeUnit::kSecond && compare(otherDate, (TimeUnit)(enum_index(unit)+1)) > 0)
                ++result;

        return result;
    }

    void Date::setToNow() {
        time_t Timestamp;
            tm *CurrentTime;

            time(&Timestamp);
            CurrentTime = localtime(&Timestamp);

            m_nArrData[enum_index(TimeUnit::kYear)] = CurrentTime->tm_year + 1900;
            m_nArrData[enum_index(TimeUnit::kMonth)] = CurrentTime->tm_mon + 1;
            m_nArrData[enum_index(TimeUnit::kDay)] = CurrentTime->tm_mday;
            m_nArrData[enum_index(TimeUnit::kHour)] = CurrentTime->tm_hour;
            m_nArrData[enum_index(TimeUnit::kMinute)] = CurrentTime->tm_min;
            m_nArrData[enum_index(TimeUnit::kSecond)] = CurrentTime->tm_sec;

           this->m_bWithTime  = true;
    }


    Recurrence::operator string() const {
        char Temp[5];
        string Text = "FREQ=";
        switch (Freq) {
            case TimeUnit::kYear: Text += "YEARLY"; break;
            case TimeUnit::kMonth: Text += "MONTHLY"; break;
            case TimeUnit::kDay: Text += "DAILY"; break;
            case TimeUnit::kHour: Text += "HOURLY"; break;
            case TimeUnit::kMinute: Text += "MINUTELY"; break;
            case TimeUnit::kSecond: Text += "SECONDLY"; break;
            case TimeUnit::kWeek: Text += "WEEKLY"; break;
        }
        Text += ";INTERVAL=";
        sprintf(Temp, "%d", Interval);
        Text += Temp;
        if (Count > 0) {
            Text += ";COUNT=";
            sprintf(Temp, "%d", Count);
            Text += Temp;
        } else if (!Until.isEmpty()) {
            Text += ";UNTIL=";
            Text += Until;
        }

        return Text;
    }

    AlarmTrigger &AlarmTrigger::operator =(const string &Text) {
        char UnitChar;
        // 1 because always at least 'P' before the value
        short i = 1;

        if (Text[0] == '-')
            Before = true;

        while (!isdigit(Text[i]))
            ++i;

        sscanf(Text.c_str()+i, "%hd%c", (short *)&Value, &UnitChar);

        switch (UnitChar) {
            case 'H': Unit = TimeUnit::kHour; break;
        case 'D': Unit = TimeUnit::kDay; break;
        case 'W': Unit = TimeUnit::kWeek; break;
        default: Unit = TimeUnit::kMinute; break;
        }

        return *this;
    }

    AlarmTrigger::operator string() const {
        string Text;
        char Temp[6];

        sprintf(Temp, "%d", Value);

        if (Before)
            Text = '-';
        Text += 'P';
        if (Unit != TimeUnit::kWeek && Unit != TimeUnit::kDay)
            Text += 'T';
        Text += Temp;
        switch (Unit) {
            case TimeUnit::kHour: Text += 'H'; break;
            case TimeUnit::kDay: Text += 'D'; break;
            case TimeUnit::kWeek: Text += 'W'; break;
            default: Text += 'M'; break;
        }

        return Text;
    }

    Alarm::operator string() const {
        string Text = "\r\nBEGIN:VALARM";
        Text += "\r\nTRIGGER:";
        Text += Trigger;
        Text += "\r\nACTION:";
        switch (Action) {
            case AUDIO: Text += "AUDIO"; break;
            case DISPLAY: Text += "DISPLAY"; break;
            case PROCEDURE: Text += "PROCEDURE"; break;
            case EMAIL: Text += "EMAIL"; break;
        }
        if (!Description.empty()) {
            Text += "\r\nDESCRIPTION:";
            Text += Description;
        }
        Text += "\r\nEND:VALARM";

        return Text;
    }

    Event::operator string() const {
        string Text = "BEGIN:VEVENT";
        Text += "\r\nUID:";
        Text += UID;
        Text += "\r\nSUMMARY:";

        Text += Summary;
        // some programs do not add this property, let's leave it when modifying
        if (!DtStamp.isEmpty()) {
            Text += "\r\nDTSTAMP:";
            Text += DtStamp;
        }
        Text += "\r\nDTSTART:";
        Text += DtStart;
        if (!DtEnd.isEmpty()) {
            Text += "\r\nDTEND:";
            Text += DtEnd;
        }
        if (!Description.empty()) {
            Text += "\r\nDESCRIPTION:";
            Text += Description;
        }
        if (!Categories.empty()) {
            Text += "\r\nCATEGORIES:";
            Text += Categories;
        }
        if (!RRule.IsEmpty()) {
            Text += "\r\nRRULE:";
            Text += RRule;
        }

        if (!Alarms->empty()) {
            for (list<Alarm>::const_iterator AlarmsIterator = Alarms->begin(); AlarmsIterator != Alarms->end(); ++AlarmsIterator) {
                Text += *AlarmsIterator;
            }
        }

        Text += "\r\nEND:VEVENT\r\n";

        return Text;
    }

    bool Event::HasAlarm(const Date &From, const Date &To) {
        if (Alarms->empty())
            return false;

        bool HasAlarm = false;
        Date AlarmDate;

//        for (list<Alarm>::iterator AlarmsIterator = Alarms->begin(); AlarmsIterator != Alarms->end(); ++AlarmsIterator) {
//            AlarmDate = DtStart;

//            if ((*AlarmsIterator).Trigger.Before)
//                AlarmDate[(*AlarmsIterator).Trigger.Unit] -= (*AlarmsIterator).Trigger.Value;
//            else
//                AlarmDate[(*AlarmsIterator).Trigger.Unit] += (*AlarmsIterator).Trigger.Value;

//            if (AlarmDate >= From && AlarmDate <= To) {
//                HasAlarm = true;
//                (*AlarmsIterator).Active = true;
//            } else {
//                (*AlarmsIterator).Active = false;
//            }
//        }

        return HasAlarm;
    }


}
